using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Collections.Generic;

namespace iPhOwa.Exchange.WebDav.Methods
{
	/// <summary>
	/// Base class for all WebDAV methods.
	/// </summary>
	internal abstract class Method
	{
		private readonly Uri path;
		private readonly OwaSession session;

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="path">The Exchange store path to run the method against.</param>
		/// <param name="session">Authentication details for this session.</param>
		protected Method(Uri path, OwaSession session)
		{
			this.path = path;
			this.session = session;
		}

		/// <summary>
		/// The name of the method
		/// </summary>
		public abstract string Name { get; }

		/// <summary>
		/// The exchange store path to search.
		/// </summary>
		public Uri Path
		{
			get { return path; }
		}

		/// <summary>
		/// The method content type.
		/// </summary>
		public virtual string ContentType
		{
			get { return "text/xml"; }
		}

		/// <summary>
		/// Build the command XML.
		/// </summary>
		protected abstract string BuildCommand();

		/// <summary>
		/// Add method specific headers to request.
		/// </summary>
		protected abstract void AddHeaders(HttpWebRequest request);

		/// <summary>
		/// Parse the namespace and name of a proprety.
		/// </summary>
		/// <param name="prop"></param>
		/// <returns></returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.String.StartsWith(System.String,System.StringComparison)")]
		protected static KeyValuePair<string, string> ParseProperty(string prop)
		{
			string ns;
			string name;

			if (prop.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
			{
				ns = prop.Substring(0, prop.LastIndexOf('/') + 1);
				name = prop.Substring(prop.LastIndexOf('/') + 1);
			}
			else
			{
				ns = prop.Substring(0, prop.LastIndexOf(':') + 1);
				name = prop.Substring(prop.LastIndexOf(':') + 1);
			}

			return new KeyValuePair<string, string>(ns, name);

		}

		/// <summary>
		/// Executes this method.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")]
		protected WebResponse Execute()
		{
			HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(path);

			if (session.AuthenticationType == AuthenticationType.Forms)
			{
				request.CookieContainer = session.Cookies;
			}
			else
			{
				CredentialCache Credentials = new CredentialCache();
				Credentials.Add(Path, "NTLM", new NetworkCredential(session.UserName, session.Password, session.Domain));

				request.Credentials = Credentials;
			}

			request.Method = Name;
			AddHeaders(request);

			string command = BuildCommand();

			if (!string.IsNullOrEmpty(command))
			{
				byte[] bytes = Encoding.UTF8.GetBytes(command);

				request.ContentLength = bytes.Length;
				request.ContentType = ContentType;

				using (Stream requestStream = request.GetRequestStream())
				{
					requestStream.Write(bytes, 0, bytes.Length);
				}
			}

			return request.GetResponse();
		}

		/// <summary>
		/// Read the stream into an Xml Document.
		/// </summary>
		protected XmlDocument ReadXml(Stream stream)
		{
			XmlDocument document = new XmlDocument();
			document.Load(stream);

			return document;
		}

		/// <summary>
		/// Builds a hash or all the properties.
		/// </summary>
		protected static PropertyDictionary ReadProperties(XmlNodeList nodes)
		{
			PropertyDictionary propertyHash = new PropertyDictionary();

			if (nodes != null)
			{
				ReadProperties(nodes, propertyHash);
			}

			return propertyHash;
		}

		private static void ReadProperties(XmlNodeList nodes, PropertyDictionary propertyHash)
		{
			foreach (XmlElement element in nodes)
			{
				if (element.LocalName.Equals("propstat") || element.LocalName.Equals("prop"))
				{
					ReadProperties(element.ChildNodes, propertyHash);
				}
				else
				{
					string key = string.Concat(element.NamespaceURI, element.LocalName);

					if (!propertyHash.ContainsKey(key))
					{
						propertyHash.Add(key, element.InnerText);
					}
				}
			}
		}
	}
}